﻿/*
 * Copyright 2011 Edward L. Stauff.                  Contact: <EdStauff@gmail.com>.
 * 
 * This file, "ConsoleUtils.cs", is part of the "Stauffware.Common" class library.
 * "Stauffware.Common" is free software: you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser Public License as published by the 
 * Free Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * "Stauffware.Common" is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with 
 * the "Stauffware.Common" sources.  If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace Stauffware.Common.Console
{
	/// <summary>
	/// Utility methods for manipulating the console.
	/// </summary>
	public static class ConsoleUtils
	{
		//	Note: this class encapsulates some non-obvious magic:
		//	"SetLastError=true" ensures that the Win32 "last error" global variable
		//		gets set; otherwise, we might not get an accurate error code.
		//	The Win32 "GetLastError" routine cannot be reliably called from C#;
		//		the approved alternative is Marshal.GetLastWin32Error.
		//	The default constructor for Win32Exception automatically calls 
		//		Marshal.GetLastWin32Error.

		[DllImport("kernel32.dll", SetLastError=true)]
		private static extern bool AllocConsole ( );

		[DllImport("kernel32.dll", SetLastError=true)]
		private static extern bool AttachConsole (int processID);

		[DllImport("kernel32.dll", SetLastError=true)]
		private static extern bool FreeConsole ( );

		[DllImport("kernel32.dll", SetLastError=true)]
		private static extern int GetConsoleWindow ( );

		//---------------------------------------------------------------------
		/// <summary>
		/// Returns whether the calling process is currently attached to a console.
		/// At startup, a "Console Application" is attached to a console (one is
		/// created if necessary), whereas a "Windows Application" is not (even if
		/// it was launched from a console).
		/// </summary>

		public static bool ConsoleExists ( )
		{
			return GetConsoleWindow() != 0;
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// Creates a new console and attaches the calling process to it.
		/// If the calling process is already attached to a console, this method
		/// detaches it before attaching it to a new console.
		/// </summary>

		public static void AttachNewConsole ( )
		{
			if (ConsoleExists())
				if (!FreeConsole())
					throw new Win32Exception();
			if (!AllocConsole())
				throw new Win32Exception();
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// Attaches the calling process to the console of its parent process.
		/// If the calling process is already attached to a console, this method
		/// detaches it before attaching it to the parent's console.
		/// </summary>
		/// <param name="attachNewIfNoParent">
		///		Whether to allocate and attach a new console if attaching to the 
		///		parent console failed.
		/// </param>

		public static void AttachToParentConsole (bool attachNewIfNoParent)
		{
			if (ConsoleExists())
				if (!FreeConsole())
					throw new Win32Exception();
			if (AttachConsole(-1))		// -1 = use parent process
				return;
			if (!attachNewIfNoParent)
				throw new Win32Exception();
			if (!AllocConsole())
				throw new Win32Exception();
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// Tries to attach the calling process to the console of its parent process.
		/// If the calling process is already attached to a console, this method
		/// detaches it before attaching it to the parent's console.
		/// Returns true on success, false on failure.
		/// </summary>
		/// <param name="attachNewIfNoParent">
		///		Whether to try allocating and attaching a new console 
		///		if attaching to the parent console failed.
		/// </param>
		/// 
		public static bool TryAttachToParentConsole (bool attachNewIfNoParent)
		{
			if (ConsoleExists())
				if (!FreeConsole())
					throw new Win32Exception();
			if (AttachConsole(-1))		// -1 = use parent process
				return true;
			if (!attachNewIfNoParent)
				return false;
			return AllocConsole();
		}

		//---------------------------------------------------------------------
	}
}
